Xcode と署名
参考文献
本ノートは、これらの情報ソースから得た知識をもって、署名周辺の知識を俯瞰できるように個人用にまとめなおしたものになります。
背景
Xcode で実機に iOS アプリケーションをインストールしよう と思った時に、Signing (署名) が必要になる。個人開発をしているときは雰囲気で Developer Program に登録して証明書を発行してなんとかしていたが、プロダクションではそうはいかず、複数の証明書を管理する必要が出てくる。
https://gyazo.com/f836ad1b81e079c8905a8d62728a07e0
なぜ必要なのか?
そもそも、コード署名 とは何なのか、というと、下記のようなことを可能にするための仕組みとなっている。 システム (iOS アプリの場合は iOS) が、そのアプリの発行元を識別できる
システムが、そのアプリが最後に署名されてから改変されていないことを確認できる
iOS アプリを App Store Connect にアップロードし、TestFlight や App Store で配信するためには、この コード署名 は必須の要件となっている。なぜ iOS アプリを実機インストールするためにこの署名が必要なのか?というと、iOS が署名されていないアプリケーションのインストールを受け付けていないから である。アプリケーションを iOS にインストールした時に、iOS はそのアプリケーションの署名をチェックする。したがって、署名されていない野良アプリケーションなどは iOS にダウンロードできないし、仮ににダウンロードできたとしても、OS がその署名の正当性を確認できなかったら、アプリケーションは実行することが出来ない。 どういう仕組みなのか?
コード署名は、基本的には一般的なデジタル署名と同様の仕組みになっているけれど、改めてその仕組みをおさらいしてみる。コード署名で実現したいことは、以下の2つである。 アプリケーションコードが改竄されていないこと
特定のユーザによって署名されていること
これらがどのような技術を用いられて実現されているのか?復讐する。
アプリケーションコードが改竄されていないことを示す
あるアプリケーションコードが改竄されていないことを示すには、どうすれば良いか?これは割と単純で、特定の単方向ハッシュ関数を使えば良い。単方向ハッシュ関数とは、同じ入力に対しては必ず同じ出力をするけれど、出力から元の入力は推測できないような関数である。
あるユーザAが、ユーザBに対し、改竄されていないことを示したいコードを持っているとする。ユーザAはまず、手元のコードを特定のハッシュ関数でハッシュ化する。そして、ハッシュ化前のコード と ハッシュ化後のハッシュ値 の両方をユーザBに送る。
ユーザBは、受け取ったハッシュ化前のコードを、ユーザAが利用したハッシュ関数と同じハッシュ関数でハッシュ化する。そうして計算結果として得られたハッシュ値が、ユーザAから送られてきたハッシュ値と一致した場合、ハッシュ化前のコードは改竄されていないことがわかる (以下概要図。Apple のドキュメントだと、計算して得られうハッシュ値は seal と呼ばれる)。
https://gyazo.com/9da1ecdb932d7cbfe86fcb9c2825247f
なぜこれでデータが改竄されていないことがわかるのか?大前提として、ハッシュ化後のデータから、ハッシュ化前のデータは導出できない というものがある。これはつまり、ハッシュ関数の計算結果として得られるハッシュ値を知っているには、必ず入力値であるハッシュ化前のコードを知っている必要がある ということになる。そのため、送られてきたハッシュ化前のコードとハッシュ値が実際に計算して得られる正しいものであるなら、そのデータの送信者は送ってきたハッシュ化前のコードを知っていたことが保証できる。
ただし、これは ハッシュ値自体が改竄されていないことが前提 になる。例えば、データが改竄された後に、ハッシュ値も計算し直されて改竄されて送られてきてしまっていたら、改竄に気づくことが出来ない。逆に、ハッシュ値が改竄されていないことが保証されていさえすれば、ハッシュ化前のコードが改竄されていないことも同時に保証できることになる。この、ハッシュ値が改竄されていないことを保証 するには、デジタル署名 の仕組みを利用する。 特定のユーザによって署名されていることを示す
デジタル署名 は、公開鍵暗号を利用した仕組みである。この仕組みによって、前述のハッシュ値が改竄されていないこと、そして、特定のユーザによって署名されていること、の双方を保証することができる。 まず、データの送信元のユーザAは、公開鍵と秘密鍵のペアを用意する。そして、公開鍵は デジタル証明書 (digital certificate) という形式で、事前にユーザBに渡しておく。デジタル証明書とは、公開鍵を配布するのに用いられるデータの集合であり、公開鍵以外にも様々なデータを含んでいるけれど、詳しくは割愛する。とりあえずは、公開鍵を渡していることだけ把握しておけば良い。
そして、先ほどと同様にコードからハッシュ値の計算を行う。この時、計算結果得られたハッシュ値はそのまま送信せず、秘密鍵で暗号化してから送信する。この時暗号化されたデータが俗に デジタル署名 (digital signature) と呼ばれる。ユーザAは、最終的に以下の3つのデータをユーザBに送信する。
デジタル証明書 (公開鍵を含む)
ハッシュ化前のデータ
デジタル署名 (秘密鍵で暗号化済のハッシュ値)
https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Art/code_signing.png
ユーザBはこれらのデータを受け取ると、まずはハッシュ化前のデータからハッシュ値を再度計算する。そして、デジタル証明書から公開鍵を取り出し、デジタル署名を複合化して、暗号化前のハッシュ値を取得する。こうして得られたハッシュ値と、計算結果として得たハッシュ値を比較し、一致するか確かめる。
このフローの中で、秘密鍵は表に出ていない。秘密鍵を利用した暗号化は秘密鍵を保有する署名者しか行えないため、これによって「秘密鍵を所有している署名者によって署名されたこと」と「送られてきたハッシュ化前のコードが改竄されていないこと」の両方が示せることになる。
https://gyazo.com/c0a3c6325c02d7a89f48ff32a77bb56d
なるほど
やっぱり署名周り何もわからんということがわかった、という話
コード署名の仕組みがわかった!しかしところで、Xcode 上で触れられる署名関係の問題に目を向けてみても、やっぱりわからないことだらけである。まず用語がわからない。Provisioning Profile とは何者なのか?Code Signing Identity とは何者なのか?他にも、コード署名 のための秘密鍵/公開鍵はどこからやってきたのか?とか、Xcode は結局何をやってくれているの?とか... iOS アプリケーションの署名にあたっては、様々な登場人物や用語が出てくるためとてもとっつきにくい。なので、このノートでは、Xcode が裏でやってくれていることも含めて、一体 コード署名 のなかで誰がどういうことをしたことによって、最終的に iOS アプリケーションが実機で動くまでに漕ぎ着けるのか?を整理していきたい。 何を用意すれば良い?
iOS アプリ開発者がなぜ署名をするのかといえば (かなり語弊があるけれど)、iOS 端末でアプリを動かしたいからである。では、iOS 端末でアプリを動かすのに必要最低限用意しなければいけないものは何なのだろうか。コード署名をする必要があるというのはわかったのだけど、具体的に iOS に何を渡せばよくて、開発者は何を用意すれば良いのだろうか?
コード署名の手順において、ユーザAがユーザBに何を送っていたか?をここで振り返ってみると、以下の3つだった。
ハッシュ化前のコード
デジタル証明書 (公開鍵を含む)
デジタル署名 (秘密鍵で暗号化したハッシュ値)
コード署名という手続きを踏んでいるのであるのだから、少なくともこれらの情報は送っているに違いない。
1つ目のハッシュ化前のコードは、誰もが当然持っている、Xcode 上で書いたアプリケーションコードが該当する。続いて、デジタル証明書が必要になる。デジタル証明書はその中に公開鍵を含むので、当然公開鍵/秘密鍵のペアも必要になる。
デジタル証明書 (Certificate) を用意する
コード署名というのは、具体的にはアプリケーションコードをハッシュ関数にかけて、得られたハッシュ値を秘密鍵で暗号化する。この時利用する秘密鍵が開発者の PC 上に存在しなければ、当然コード署名も実行できない。このための秘密鍵および公開鍵のキーペアは、KeyChain Certificate Assistant を利用して生成できる。KeyChain 自体は macOS/iOS の利用者には馴染み深いかもしれない。ざっくりいうと、パスワード等の秘匿情報を管理してくれるツールである。Certificate Assistant は KeyChain の中の一機能と捉えておけば良い。秘密鍵/公開鍵のキーペア を生成した場合、その秘密鍵の扱いは、漏洩しないように慎重に行う必要がある。KeyChain を利用して秘密鍵/公開鍵を生成すると、KeyChain は生成した秘密鍵の方を安全に保存しておいてくれる。
しかし、公開鍵はデジタル証明書の形式で配布する必要があり、このデジタル証明書の作成を行う必要がある。デジタル証明書の作成は、通常 認証局 (Certificate Authority) に、公開鍵と共に証明書作成のリクエストを投げて作成する、という手順をふむ。そして、iOS アプリケーション開発において、この証明書のリクエスト先である認証局とは、大抵 Apple Developer Program の Member Center になる。
つまり、KeyChain で秘密鍵/公開鍵のキーペア を作成したら、その公開鍵を持って Apple Developer Program に証明書の発行をリクエストしに行けば良い。
デジタル証明書を得るための具体的な手順は、下記のようになる。
1. KeyChain Certificate Assistant にて、Certificate Signing Request ファイル (.certSigningRequest, CSR ファイル) を作成
2. 作成した CSR ファイルを Apple Developer Program にアップロードする
3. Apple Developer Program からデジタル証明書をダウンロードする
CSR には実際には公開鍵ファイルが含まれている。これを Apple Developer Program にアップロードすると、必要に応じて様々な情報が付加されて、最終的にデジタル証明書 (Certificate) の形式で手元にダウンロードできる、という流れ。
Certificate Assistant には下記のようにアクセスする。「認証局に証明書を要求」みたいなメニューがあるのでそちらを選択する。
https://gyazo.com/30dce6daf951ab44f70a2affcaffce36
下記のような画面になるので、Email アドレスと名前を入力し、CA のアドレスは未入力にする。この時の Email アドレスについて、適当でいいとか適当な意見をよく見かけるのだけど、自分もイマイチわかっておらず迂闊なことが書けない... Apple Developer Program に対して証明書を要求するときには、とりあえず Apple Developer Program に登録してあるメールアドレスを登録しておけば問題はなさそうに思える。
https://gyazo.com/36f19d55de864fe0e6f6bda67464f05b
上記の手順を経て CSR ファイルを得た段階で、KeyChain は内部的に秘密鍵を保持してくれている。あとは、公開鍵から証明書を生成して証明書ファイルである .cer ファイルを DL すれば良い。(詳しくいうと中間証明書というものがこのとき利用されていたりするらしいのだけど、詳しくは割愛する)
証明書のリクエストは Web 上から行う。現状だと、様々な種類の証明書が、Apple Developer Program の Web サイト上で選択できる。このとき選択する証明書の種類によって、公開鍵は一緒だけど、その他に含まれる追加情報が異なってくる。そして、特定の用途のためには、同一の公開鍵を利用していても異なる種類の証明書が必要だったりする。例えば、macOS の App Store 配布に必要な証明書と、iOS の App Store 配布に必要な証明書は異なるし、App Store 経由以外で public にアプリを公開する場合にも専用の Certificate があったりする。リリースが InHouse か?AdHoc か?等によっても変わってくるらしい。証明書の種類には注意しなくてはいけない。
ここまでで、ハッシュ化前のコードはあるし、KeyChain によって秘密鍵/公開鍵のペアも作成した。Apple の Member Center からデジタル証明書も DL した。これで、コード署名においてユーザB、すなわちアプリを DL する先である、iOS に送りつけるべきものは手元に全て揃った、ように見える。
そういえば、ここまでで、KeyChain 内に秘密鍵/公開鍵のペアは存在するけれど、秘密鍵と証明書のペア はどこにも管理されていない。おそらく手元に証明書はダウンロードしてあるだろうけれど、これがどの秘密鍵と紐付いたものか?というのは、ユーザからだとちょっとわかりにくい。
そこで、KeyChain 上で、証明書と秘密鍵をペアにして管理させておくことができる。このような、秘密鍵と証明書のペア のことは Identity (署名) と呼ばれ、さらに今回のようなコード署名の文脈における Identity は、特に Code Signing Identity と呼ばれる。Code Signing Identity は、DL した .cer ファイルをダブルクリック、もしくは KeyChain にドラッグ&ドロップすると自動で作成されるようだ。コード署名を行うためには、事前にこの形式で証明書と秘密鍵のペアを管理しておく必要があるらしい。 ちなみに、Code Signing Identity は .p12 ファイルというファイルフォーマットで KeyChain からエクスポート/インポートできてしまう。.p12 ファイルには、公開鍵および証明書のみでなく、コード署名の要である秘密鍵も含まれているので、注意して扱う必要がある。逆に、自分の PC 上からエクスポートした .p12 ファイルを他の PC にインポートすれば、その他の PC でコード署名が行えてしまう。
Provisioning Profile を用意する
これでコード署名に必要そうなものは全て揃ったし、秘密鍵と証明書のペアも KeyChain に管理させることが出来た。いよいよ iOS にアプリをインストールできるだろうか?
結論から言うとまだ必要なものが足りていない。なぜか?iOS は、コード署名によるコードの改竄や署名者の特定意外にも、いくつか追加で確認している情報がいくつかあり、それらの情報がまだ用意できていないためである。この「他にも iOS が追加で確認している情報」がまとめられたものが、Provisioning Profile である。 開発中の iOS アプリは、どんな iOS 端末にも好き勝手にダウンロードできる、と言うわけではなく、事前に許可を得た iOS 端末に、許可を得たアプリケーションのみがダウンロードできるようになっている。どのデバイスでどのアプリをダウンロード可能にするか?は、Apple Develoepr Program の Web ページ上から設定できる。基本的には、Provisioning Profile には以下が含まれる。
対象のデジタル証明書 (Certificate)
既に DL して Code Signing Identity としてローカルで管理しているもの
Apple Developer Program 上でも管理されており、参照できるようになっている
複数人で開発している場合には、チームメンバー全員の証明書が Provisioning Profile に含まれている必要がある
対象のアプリケーションの App ID
対象の iOS 端末のデバイスID
Provisioning Profile の作成, インストールは、Apple Developer Program の Web サイト上から実施できる。この Provisioning Profile もダウンロードしておき、KeyChain 上で管理する。
まとめ
全体の流れ
ここまででようやく、必要なものが全て揃ったと思う。改めて大まかな流れを以下に記しておく。
1. KeyChain から Certificate Signing Request (CSR) を作成する
作成すると、秘密鍵 と certSigningRequest が作成される
2. certSigningRequest を Apple に提出する
Member Center からアップロードできる
3. Apple によって Certificate (公開鍵) が発行される
4. Certificate を KeyChain に追加すると、Code Signing Identity が作成される
5. Member Center 上で、Provisioning Profile を用意し、対象の証明書やデバイス、App ID を選択しておく
https://gyazo.com/66fce69d90856e910e3fa065055e9423
大まかな概要図で言うと、Adobe の下記の図もわかりやすい感じだった。大体雰囲気が掴めると思う。
https://www.adobe.com/content/dam/acom/jp/devnet/images/large/fig00.jpg
Xcode は何をしてくれるか?
ここまで、手動で証明書を作成等する手順を説明したけれど、実は Xcode はここら辺の作業を自動で全て行ってくれる機能がある。Xcode は Apple Developer Program の Member Center と裏で通信をしており、Code Signing Identity や Certificate の作成, Provisioning Profile の作成等を自動化できる。ただ、個人開発だとこれに頼っていれば十分だけど、仕事で様々な環境で複数種類の証明書を利用する必要がある場合は、これは利用できない。
Xcode は、アプリケーションコードのビルド時に、コードをハッシュ化後暗号化するコード署名を実行する。また、Provisioning Profile を iOS アプリに push するのも Xcode が行う。push された iOS は、そこに含まれる Device ID や App ID が正しいか?コード署名が正しいか?チェックを行い、チェックを通れば晴れてアプリケーションを実行することが可能になる。
その他の話
証明書の期限切れ
作成したデジタル証明書は、通常一年で期限が切れてしまう。そのため、一年ごとに更新が必要になる。これが案外面倒くさい。
証明書が更新されるということは、証明書を参照している Provisioning Profile も更新する必要があるということになる。面倒くさい。
応用編: fastlane match を使用した証明書管理
詳しくは以下にありますが。
証明書周りでやるべきタスクというのは案外多くて、その割に頻度は少ないので毎度やることを忘れてしまいとても手間がかかるという問題がある。
ここまで読んでいたらわかるように、チームにメンバーが増えた場合は、まず PC 上で秘密鍵/公開鍵のペアを作成し、公開鍵から新しく証明書を作成する必要がある。そして、既存のアプリで利用していた Provisioning Profile に、新しいチームメンバーが作成した新しい証明書を含める必要がある。
これは、admin が作成した Provisioning Profile および証明書を GitHub にアップロードし、それをチームメンバー間で共有するという仕組みのようで、CLI 経由で実行できるので自動化が可能で嬉しい。
メモ
気持ちが沸いたら調べたいことをつらつらとメモしておく。
そのほか知りたいこと
App ID と Xcode 上の Debug や release とうのステージ関係の設定は、どこで行われるのか?
Xcode、というかプロジェクト上の証明書周りの設定 (多分 info.plist あたりにある) はどうなっているのか?
複数人開発における証明書管理のプラクティスにはどのようなものがあるのか?
マッチ、というものがあるようだ
権限管理周りが面倒そう
.p12 を直接使う場合と使わない場合で別れているのだけど、これはセキュリティ上の問題かな?
Xcode から直接証明書発行する手順と、KeyChain 使って自分で認証局署名を行うパターン、具体的な違いを知りたい
具体的には、Xcode 側で完結する場合の手順を知っておきたいので、マニュアルをあとで読む
$ codesign --force --sign - --timestamp=none /Users/tasuwo/workspace/sandbox/XVim2/build/Release/XVim2.xcplugin
unsigned は XVim の奴に従うのが良い。そうしないとフリーズしまくる
下記のようなエラーが出たときの対処法とか
code:text
Provisioning profile "MyApp" doesn't include signing certificate "Apple Development: tasuku tozawa (XXXXXXX)".
Provisioning profile "MyApp" doesn't include signing certificate "Apple Distribution: XXX(XXX)".
Library validation について (WIP)
とは、iOS8, macOS 10.10 から始まったもので、「プログラムは、そのコード署名に含まれるチームIDと同一のチームIDを含んだコード署名をされているライブラリや、Appleシステムライブラリとしかリンクされない」と言うもの。
iOS の場合はシステムライブラリはOSイメージに内包される
macOS の場合は /System/Library 以下にある
チームIDは10桁の文字列で、developer account にひも付き、Apple の発行した証明書に記録されている
iOS の場合はこの昨日は全てのアプリで有効になっている
macOSの場合、 codesign おフラグに library フラグをつける必要がある